<template>
  <component :is="tagName" :class="tagClassname">
    <slot name="reference"></slot>
    <div ref="popper" v-show="!disabled && showPopper">
      <slot></slot>
    </div>
  </component>
</template>

<script>
import Popper from "popper.js";
const on = (element, event, handler) => {
  if (element && event && handler) {
    document.addEventListener
      ? element.addEventListener(event, handler, false)
      : element.attachEvent("on" + event, handler);
  }
};
const off = (element, event, handler) => {
  if (element && event) {
    document.removeEventListener
      ? element.removeEventListener(event, handler, false)
      : element.detachEvent("on" + event, handler);
  }
};
export default {
  name: "TdPopper",
  props: {
    tagName: {
      type: String,
      default: "div"
    },
    tagClassname: {
      type: String
    },
    trigger: {
      type: String,
      default: "hover",
      validator: value =>
        [
          "clickToOpen",
          "click", // Same as clickToToggle, provided for backwards compatibility.
          "clickToToggle",
          "hover",
          "focus"
        ].indexOf(value) > -1
    },
    delayOnMouseOver: {
      type: Number,
      default: 25
    },
    delayOnMouseOut: {
      type: Number,
      default: 100
    },
    disabled: {
      type: Boolean,
      default: false
    },
    boundariesSelector: String,
    reference: {},
    forceShow: {
      type: Boolean,
      default: false
    },
    dataValue: {
      default: null
    },
    appendToBody: {
      type: Boolean,
      default: false
    },
    stopPropagation: {
      type: Boolean,
      default: false
    },
    preventDefault: {
      type: Boolean,
      default: false
    },
    options: {
      type: Object,
      default() {
        return {};
      }
    }
  },
  data() {
    return {
      referenceElm: null,
      popperJS: null,
      showPopper: false,
      popperOptions: {
        placement: "bottom-start",
        computeStyle: {
          gpuAcceleration: false
        }
      }
    };
  },
  watch: {
    showPopper(value) {
      if (value) {
        this.$emit("show", this);
        if (this.popperJS) {
          this.popperJS.enableEventListeners();
        }
        this.updatePopper();
      } else {
        if (this.popperJS) {
          this.popperJS.disableEventListeners();
        }
        this.$emit("hide", this);
      }
    },
    forceShow: {
      handler(value) {
        this[value ? "doShow" : "doClose"]();
      },
      immediate: true
    },
    disabled(value) {
      if (value) {
        this.showPopper = false;
      }
    }
  },
  created() {
    this.appendedArrow = false;
    this.appendedToBody = false;
    this.popperOptions = Object.assign(this.popperOptions, this.options);
  },
  mounted() {
    this.referenceElm = this.reference || this.$slots.reference[0].elm;
    this.popper = this.$slots.default[0].elm;
    switch (this.trigger) {
      case "clickToOpen":
        on(this.referenceElm, "click", this.doShow);
        on(document, "click", this.handleDocumentClick);
        break;
      case "click": // Same as clickToToggle, provided for backwards compatibility.
      case "clickToToggle":
        on(this.referenceElm, "click", this.doToggle);
        on(document, "click", this.handleDocumentClick);
        break;
      case "hover":
        on(this.referenceElm, "mouseover", this.onMouseOver);
        on(this.popper, "mouseover", this.onMouseOver);
        on(this.referenceElm, "mouseout", this.onMouseOut);
        on(this.popper, "mouseout", this.onMouseOut);
        break;
      case "focus":
        on(this.referenceElm, "focus", this.onMouseOver);
        on(this.popper, "focus", this.onMouseOver);
        on(this.referenceElm, "blur", this.onMouseOut);
        on(this.popper, "blur", this.onMouseOut);
        break;
    }
  },
  methods: {
    doToggle(event) {
      if (this.stopPropagation) {
        event.stopPropagation();
      }
      if (this.preventDefault) {
        event.preventDefault();
      }
      if (!this.forceShow) {
        this.showPopper = !this.showPopper;
      }
    },
    doShow() {
      this.showPopper = true;
    },
    doClose() {
      this.showPopper = false;
    },
    doDestroy() {
      if (this.showPopper) {
        return;
      }
      if (this.popperJS) {
        this.popperJS.destroy();
        this.popperJS = null;
      }
      if (this.appendedToBody) {
        this.appendedToBody = false;
        document.body.removeChild(this.popper.parentElement);
      }
    },
    createPopper() {
      this.$nextTick(() => {
        if (this.appendToBody && !this.appendedToBody) {
          this.appendedToBody = true;
          document.body.appendChild(this.popper.parentElement);
        }
        if (this.popperJS && this.popperJS.destroy) {
          this.popperJS.destroy();
        }
        if (this.boundariesSelector) {
          const boundariesElement = document.querySelector(
            this.boundariesSelector
          );
          if (boundariesElement) {
            this.popperOptions.modifiers = Object.assign(
              {},
              this.popperOptions.modifiers
            );
            this.popperOptions.modifiers.preventOverflow = Object.assign(
              {},
              this.popperOptions.modifiers.preventOverflow
            );
            this.popperOptions.modifiers.preventOverflow.boundariesElement = boundariesElement;
          }
        }
        this.popperOptions.onCreate = () => {
          this.$emit("created", this);
          this.$nextTick(this.updatePopper);
        };
        this.popperJS = new Popper(
          this.referenceElm,
          this.popper,
          this.popperOptions
        );
      });
    },
    destroyPopper() {
      off(this.referenceElm, "click", this.doToggle);
      off(this.referenceElm, "mouseup", this.doClose);
      off(this.referenceElm, "mousedown", this.doShow);
      off(this.referenceElm, "focus", this.doShow);
      off(this.referenceElm, "blur", this.doClose);
      off(this.referenceElm, "mouseout", this.onMouseOut);
      off(this.referenceElm, "mouseover", this.onMouseOver);
      off(document, "click", this.handleDocumentClick);
      this.showPopper = false;
      this.doDestroy();
    },
    updatePopper() {
      this.popperJS ? this.popperJS.scheduleUpdate() : this.createPopper();
    },
    onMouseOver() {
      clearTimeout(this._timer);
      this._timer = setTimeout(() => {
        this.showPopper = true;
      }, this.delayOnMouseOver);
    },
    onMouseOut() {
      clearTimeout(this._timer);
      this._timer = setTimeout(() => {
        this.showPopper = false;
      }, this.delayOnMouseOut);
    },
    handleDocumentClick(e) {
      if (
        !this.$el ||
        !this.referenceElm ||
        this.elementContains(this.$el, e.target) ||
        this.elementContains(this.referenceElm, e.target) ||
        !this.popper ||
        this.elementContains(this.popper, e.target)
      ) {
        return;
      }
      this.$emit("documentClick", this);
      if (this.forceShow) {
        return;
      }
      this.showPopper = false;
    },
    elementContains(elm, otherElm) {
      if (typeof elm.contains === "function") {
        return elm.contains(otherElm);
      }
      return false;
    }
  },
  destroyed() {
    this.destroyPopper();
  }
};
</script>